/*-------------------------------------------------------------------------
 *
 * copyfrom.c
 *		COPY <table> 从文件/程序/客户端
 *
 * 此文件包含将元组有效加载到表中的所需例程。  包括查找正确的分区、触发器、调用表 AM 函数插入数据和更新索引。 从输入文件或客户端读取数据并将其解析为 Datums 的操作在 copyfromparse.c 中处理。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/copyfrom.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/namespace.h"
#include "commands/copy.h"
#include "commands/copyfrom_internal.h"
#include "commands/progress.h"
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
#include "executor/tuptable.h"
#include "foreign/fdwapi.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
#include "pgstat.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/portal.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"

/*
 * 每个 CopyMultiInsertBuffer 的元组数量不能超过这个值
 *
 * 注意：不要设置得太大，因为我们可能会在 CopyMultiInsertInfo 的
 * multiInsertBuffers 列表中存储这么多 CopyMultiInsertBuffer 项目。
 * 增加这个值可能会导致在向具有大量分区的分区表进行复制时内存需求
 * 以平方级增长。
 */
#define MAX_BUFFERED_TUPLES		1000

/*
 * 如果存储的元组字节数大于等于这个值，则刷新缓冲区。
 */
#define MAX_BUFFERED_BYTES		65535

/* 刷新后将缓冲区列表缩减到这个数量 */
#define MAX_PARTITION_BUFFERS	32

/* 存储与 CopyFrom 中单个关系相关的多插入数据。 */
typedef struct CopyMultiInsertBuffer
{
	TupleTableSlot *slots[MAX_BUFFERED_TUPLES]; /* 用于存储元组的数组 */
	ResultRelInfo *resultRelInfo;	/* 'relid' 的 ResultRelInfo */
	BulkInsertState bistate;	/* 该关系的 BulkInsertState */
	int			nused;			/* 包含元组的 'slots' 数量 */
	uint64		linenos[MAX_BUFFERED_TUPLES];	/* 在复制流中元组的行号 */
} CopyMultiInsertBuffer;

/*
 * 存储一个或多个 CopyMultiInsertBuffers 及其存储的元组的大小和数量细节。
 * 这允许在向分区表复制数据时同时存在多个缓冲区。
 */
typedef struct CopyMultiInsertInfo
{
	List	   *multiInsertBuffers; /* 跟踪的 CopyMultiInsertBuffers 列表 */
	int			bufferedTuples; /* 所有缓冲区中缓存的元组数量 */
	int			bufferedBytes;	/* 所有缓冲元组的字节数 */
	CopyFromState cstate;		/* 该 CopyMultiInsertInfo 的复制状态 */
	EState	   *estate;			/* 用于 COPY 的执行器状态 */
	CommandId	mycid;			/* 用于 COPY 的命令 ID */
	int			ti_options;		/* 表插入选项 */
} CopyMultiInsertInfo;


/* 非导出函数原型 */
static char *fc_limit_printout_length(const char *fc_str);

static void fc_ClosePipeFromProgram(CopyFromState fc_cstate);

/*
 * COPY FROM 的错误上下文回调
 *
 * 错误上下文的参数必须是 CopyFromState。
 */
void CopyFromErrorCallback(void *fc_arg)
{
	CopyFromState fc_cstate = (CopyFromState) fc_arg;

	if (fc_cstate->opts.binary)
	{
		/* 不能有效地显示数据 */
		if (fc_cstate->cur_attname)
			errcontext("COPY %s, line %llu, column %s",
					   fc_cstate->cur_relname,
					   (unsigned long long) fc_cstate->cur_lineno,
					   fc_cstate->cur_attname);
		else
			errcontext("COPY %s, line %llu",
					   fc_cstate->cur_relname,
					   (unsigned long long) fc_cstate->cur_lineno);
	}
	else
	{
		if (fc_cstate->cur_attname && fc_cstate->cur_attval)
		{
			/* 错误与特定列相关 */
			char	   *fc_attval;

			fc_attval = fc_limit_printout_length(fc_cstate->cur_attval);
			errcontext("COPY %s, line %llu, column %s: \"%s\"",
					   fc_cstate->cur_relname,
					   (unsigned long long) fc_cstate->cur_lineno,
					   fc_cstate->cur_attname,
					   fc_attval);
			pfree(fc_attval);
		}
		else if (fc_cstate->cur_attname)
		{
			/* 错误与特定列相关，值为 NULL */
			errcontext("COPY %s, line %llu, column %s: null input",
					   fc_cstate->cur_relname,
					   (unsigned long long) fc_cstate->cur_lineno,
					   fc_cstate->cur_attname);
		}
		else
		{
			/*
			 * 错误与特定行相关。
			 *
			 * 如果 line_buf 仍然包含正确的行，则打印它。
			 */
			if (fc_cstate->line_buf_valid)
			{
				char	   *fc_lineval;

				fc_lineval = fc_limit_printout_length(fc_cstate->line_buf.data);
				errcontext("COPY %s, line %llu: \"%s\"",
						   fc_cstate->cur_relname,
						   (unsigned long long) fc_cstate->cur_lineno, fc_lineval);
				pfree(fc_lineval);
			}
			else
			{
				errcontext("COPY %s, line %llu",
						   fc_cstate->cur_relname,
						   (unsigned long long) fc_cstate->cur_lineno);
			}
		}
	}
}

/*
 * 确保我们在消息中不会打印出不合理数量的 COPY 数据。
 *
 * 返回输入的 pstrdup 副本。
 */
static char * fc_limit_printout_length(const char *fc_str)
{
#define MAX_COPY_DATA_DISPLAY 100

	int			fc_slen = strlen(fc_str);
	int			fc_len;
	char	   *fc_res;

	/* 如果确定没问题则快速路径 */
	if (fc_slen <= MAX_COPY_DATA_DISPLAY)
		return pstrdup(fc_str);

	/* 应用依赖于编码的截断 */
	fc_len = pg_mbcliplen(fc_str, fc_slen, MAX_COPY_DATA_DISPLAY);

	/*
	 * 截断，并添加“...”以显示我们截断了输入。
	 */
	fc_res = (char *) palloc(fc_len + 4);
	memcpy(fc_res, fc_str, fc_len);
	strcpy(fc_res + fc_len, "...");

	return fc_res;
}

/*
 * 为该 ResultRelInfo 分配内存并初始化一个新的 CopyMultiInsertBuffer。
 */
static CopyMultiInsertBuffer *
fc_CopyMultiInsertBufferInit(ResultRelInfo *fc_rri)
{
	CopyMultiInsertBuffer *fc_buffer;

	fc_buffer = (CopyMultiInsertBuffer *) palloc(sizeof(CopyMultiInsertBuffer));
	memset(fc_buffer->slots, 0, sizeof(TupleTableSlot *) * MAX_BUFFERED_TUPLES);
	fc_buffer->resultRelInfo = fc_rri;
	fc_buffer->bistate = GetBulkInsertState();
	fc_buffer->nused = 0;

	return fc_buffer;
}

/*
 * 为该 ResultRelInfo 创建一个新的缓冲区。
 */
static inline void fc_CopyMultiInsertInfoSetupBuffer(CopyMultiInsertInfo *fc_miinfo,
							   ResultRelInfo *fc_rri)
{
	CopyMultiInsertBuffer *fc_buffer;

	fc_buffer = fc_CopyMultiInsertBufferInit(fc_rri);

	/* 设置反向链接，以便我们可以轻松找到该缓冲区 */
	fc_rri->ri_CopyMultiInsertBuffer = fc_buffer;
	/* 记录我们正在跟踪该缓冲区 */
	fc_miinfo->multiInsertBuffers = lappend(fc_miinfo->multiInsertBuffers, fc_buffer);
}

/*
 * 初始化一个已经分配的 CopyMultiInsertInfo。
 *
 * 如果 rri 是一个非分区表，则会为该表设置一个 CopyMultiInsertBuffer。
 */
static void fc_CopyMultiInsertInfoInit(CopyMultiInsertInfo *fc_miinfo, ResultRelInfo *fc_rri,
						CopyFromState fc_cstate, EState *fc_estate, CommandId fc_mycid,
						int fc_ti_options)
{
	fc_miinfo->multiInsertBuffers = NIL;
	fc_miinfo->bufferedTuples = 0;
	fc_miinfo->bufferedBytes = 0;
	fc_miinfo->cstate = fc_cstate;
	fc_miinfo->estate = fc_estate;
	fc_miinfo->mycid = fc_mycid;
	fc_miinfo->ti_options = fc_ti_options;

	/*
	 * 仅在不处理分区表时设置缓冲区。
	 * 对于分区表，只有在第一次需要发送元组时才会设置缓冲区。
	 */
	if (fc_rri->ri_RelationDesc->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		fc_CopyMultiInsertInfoSetupBuffer(fc_miinfo, fc_rri);
}

/*
 * 如果缓冲区满则返回 true
 */
static inline bool fc_CopyMultiInsertInfoIsFull(CopyMultiInsertInfo *fc_miinfo)
{
	if (fc_miinfo->bufferedTuples >= MAX_BUFFERED_TUPLES ||
		fc_miinfo->bufferedBytes >= MAX_BUFFERED_BYTES)
		return true;
	return false;
}

/*
 * 如果没有缓存的元组则返回 true
 */
static inline bool fc_CopyMultiInsertInfoIsEmpty(CopyMultiInsertInfo *fc_miinfo)
{
	return fc_miinfo->bufferedTuples == 0;
}

/*
 * 将存储在 'buffer' 中的元组写入表中。
 */
static inline void fc_CopyMultiInsertBufferFlush(CopyMultiInsertInfo *fc_miinfo,
						   CopyMultiInsertBuffer *fc_buffer)
{
	MemoryContext fc_oldcontext;
	int			fc_i;
	uint64		fc_save_cur_lineno;
	CopyFromState fc_cstate = fc_miinfo->cstate;
	EState	   *fc_estate = fc_miinfo->estate;
	CommandId	fc_mycid = fc_miinfo->mycid;
	int			fc_ti_options = fc_miinfo->ti_options;
	bool		fc_line_buf_valid = fc_cstate->line_buf_valid;
	int			fc_nused = fc_buffer->nused;
	ResultRelInfo *fc_resultRelInfo = fc_buffer->resultRelInfo;
	TupleTableSlot **fc_slots = fc_buffer->slots;

	/*
	 * 正确打印错误上下文信息，如果下面的任何操作失败。
	 */
	fc_cstate->line_buf_valid = false;
	fc_save_cur_lineno = fc_cstate->cur_lineno;

	/*
	 * table_multi_insert 可能会导致内存泄漏，所以在调用它之前切换到短期内存上下文。
	 */
	fc_oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(fc_estate));
	table_multi_insert(fc_resultRelInfo->ri_RelationDesc,
					   fc_slots,
					   fc_nused,
					   fc_mycid,
					   fc_ti_options,
					   fc_buffer->bistate);
	MemoryContextSwitchTo(fc_oldcontext);

	for (fc_i = 0; fc_i < fc_nused; fc_i++)
	{
		/*
		 * 如果有任何索引，更新所有插入的元组，并运行 AFTER ROW INSERT 触发器。
		 */
		if (fc_resultRelInfo->ri_NumIndices > 0)
		{
			List	   *fc_recheckIndexes;

			fc_cstate->cur_lineno = fc_buffer->linenos[fc_i];
			fc_recheckIndexes =
				ExecInsertIndexTuples(fc_resultRelInfo,
									  fc_buffer->slots[fc_i], fc_estate, false, false,
									  NULL, NIL);
			ExecARInsertTriggers(fc_estate, fc_resultRelInfo,
								 fc_slots[fc_i], fc_recheckIndexes,
								 fc_cstate->transition_capture);
			list_free(fc_recheckIndexes);
		}

		/*
		 * 没有索引，但看看我们是否仍然需要运行 AFTER ROW INSERT 触发器。
		 */
		else if (fc_resultRelInfo->ri_TrigDesc != NULL &&
				 (fc_resultRelInfo->ri_TrigDesc->trig_insert_after_row ||
				  fc_resultRelInfo->ri_TrigDesc->trig_insert_new_table))
		{
			fc_cstate->cur_lineno = fc_buffer->linenos[fc_i];
			ExecARInsertTriggers(fc_estate, fc_resultRelInfo,
								 fc_slots[fc_i], NIL, fc_cstate->transition_capture);
		}

		ExecClearTuple(fc_slots[fc_i]);
	}

	/* 标记所有槽位为空 */
	fc_buffer->nused = 0;

	/* 将 cur_lineno 和 line_buf_valid 重置为它们之前的值 */
	fc_cstate->line_buf_valid = fc_line_buf_valid;
	fc_cstate->cur_lineno = fc_save_cur_lineno;
}

/*
 * 删除已使用的槽位并释放此缓冲区的成员。
 *
 * 在清理之前，必须刷新缓冲区。
 */
static inline void fc_CopyMultiInsertBufferCleanup(CopyMultiInsertInfo *fc_miinfo,
							 CopyMultiInsertBuffer *fc_buffer)
{
	int			fc_i;

	/* 确保缓冲区已被刷新 */
	Assert(fc_buffer->nused == 0);

	/* 移除与我们自身的反向链接 */
	fc_buffer->resultRelInfo->ri_CopyMultiInsertBuffer = NULL;

	FreeBulkInsertState(fc_buffer->bistate);

	/* 由于我们只按需创建槽位，因此仅删除非空槽位。 */
	for (fc_i = 0; fc_i < MAX_BUFFERED_TUPLES && fc_buffer->slots[fc_i] != NULL; fc_i++)
		ExecDropSingleTupleTableSlot(fc_buffer->slots[fc_i]);

	table_finish_bulk_insert(fc_buffer->resultRelInfo->ri_RelationDesc,
							 fc_miinfo->ti_options);

	pfree(fc_buffer);
}

/*
 * 将所有存储的元组写入所有缓冲区并写入表中。
 *
 * 一旦刷新，我们还会通过先删除最早创建的缓冲区来修剪跟踪的缓冲区列表。
 *
 * 调用者应传递 'curr_rri' 作为当前使用的 ResultRelInfo。当清理旧缓冲区时，我们永远不会移除 'curr_rri' 的缓冲区。
 */
static inline void fc_CopyMultiInsertInfoFlush(CopyMultiInsertInfo *fc_miinfo, ResultRelInfo *fc_curr_rri)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_miinfo->multiInsertBuffers)
	{
		CopyMultiInsertBuffer *fc_buffer = (CopyMultiInsertBuffer *) lfirst(fc_lc);

		fc_CopyMultiInsertBufferFlush(fc_miinfo, fc_buffer);
	}

	fc_miinfo->bufferedTuples = 0;
	fc_miinfo->bufferedBytes = 0;

	/*
	 * 如果跟踪的缓冲区列表超过限制，则将其修剪。这里我们从最早创建的缓冲区开始删除。
	 * 这些较旧的缓冲区被需要的可能性似乎低于刚创建的那些。
	 */
	while (list_length(fc_miinfo->multiInsertBuffers) > MAX_PARTITION_BUFFERS)
	{
		CopyMultiInsertBuffer *fc_buffer;

		fc_buffer = (CopyMultiInsertBuffer *) linitial(fc_miinfo->multiInsertBuffers);

		/*
		 * 我们永远不想移除当前正在使用的缓冲区，因此如果碰巧找到它，就将其移动到列表的末尾。
		 */
		if (fc_buffer->resultRelInfo == fc_curr_rri)
		{
			fc_miinfo->multiInsertBuffers = list_delete_first(fc_miinfo->multiInsertBuffers);
			fc_miinfo->multiInsertBuffers = lappend(fc_miinfo->multiInsertBuffers, fc_buffer);
			fc_buffer = (CopyMultiInsertBuffer *) linitial(fc_miinfo->multiInsertBuffers);
		}

		fc_CopyMultiInsertBufferCleanup(fc_miinfo, fc_buffer);
		fc_miinfo->multiInsertBuffers = list_delete_first(fc_miinfo->multiInsertBuffers);
	}
}

/*
 * 清理已分配的缓冲区并释放内存
 */
static inline void fc_CopyMultiInsertInfoCleanup(CopyMultiInsertInfo *fc_miinfo)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_miinfo->multiInsertBuffers)
		fc_CopyMultiInsertBufferCleanup(fc_miinfo, lfirst(fc_lc));

	list_free(fc_miinfo->multiInsertBuffers);
}

/*
 * 获取下一个 TupleTableSlot，以便下一个元组存储在其中。
 *
 * 调用者必须确保缓冲区未满。
 *
 * 注意：'miinfo' 未使用，但出于与该区域其他函数的一致性已包含在内。
 */
static inline TupleTableSlot *
fc_CopyMultiInsertInfoNextFreeSlot(CopyMultiInsertInfo *fc_miinfo,
								ResultRelInfo *fc_rri)
{
	CopyMultiInsertBuffer *fc_buffer = fc_rri->ri_CopyMultiInsertBuffer;
	int			fc_nused = fc_buffer->nused;

	Assert(fc_buffer != NULL);
	Assert(fc_nused < MAX_BUFFERED_TUPLES);

	if (fc_buffer->slots[fc_nused] == NULL)
		fc_buffer->slots[fc_nused] = table_slot_create(fc_rri->ri_RelationDesc, NULL);
	return fc_buffer->slots[fc_nused];
}

/*
 * 记录之前由 CopyMultiInsertInfoNextFreeSlot 保留的 TupleTableSlot 已被消耗。
 */
static inline void fc_CopyMultiInsertInfoStore(CopyMultiInsertInfo *fc_miinfo, ResultRelInfo *fc_rri,
						 TupleTableSlot *fc_slot, int fc_tuplen, uint64 fc_lineno)
{
	CopyMultiInsertBuffer *fc_buffer = fc_rri->ri_CopyMultiInsertBuffer;

	Assert(fc_buffer != NULL);
	Assert(fc_slot == fc_buffer->slots[fc_buffer->nused]);

	/* 存储行号以便我们可以在稍后正确报告任何错误 */
	fc_buffer->linenos[fc_buffer->nused] = fc_lineno;

	/* 将此槽位记录为已使用 */
	fc_buffer->nused++;

	/* 更新存储的元组数量及其大小 */
	fc_miinfo->bufferedTuples++;
	fc_miinfo->bufferedBytes += fc_tuplen;
}

/*
 * 从文件复制到关系中。
 */
uint64 CopyFrom(CopyFromState fc_cstate)
{
	ResultRelInfo *fc_resultRelInfo;
	ResultRelInfo *fc_target_resultRelInfo;
	ResultRelInfo *fc_prevResultRelInfo = NULL;
	EState	   *fc_estate = CreateExecutorState(); /* 用于 ExecConstraints() */
	ModifyTableState *fc_mtstate;
	ExprContext *fc_econtext;
	TupleTableSlot *fc_singleslot = NULL;
	MemoryContext fc_oldcontext = CurrentMemoryContext;

	PartitionTupleRouting *fc_proute = NULL;
	ErrorContextCallback fc_errcallback;
	CommandId	fc_mycid = GetCurrentCommandId(true);
	int			fc_ti_options = 0; /* 从默认选项开始插入 */
	BulkInsertState fc_bistate = NULL;
	CopyInsertMethod fc_insertMethod;
	CopyMultiInsertInfo fc_multiInsertInfo = {0};	/* 使编译器满意 */
	int64		fc_processed = 0;
	int64		fc_excluded = 0;
	bool		fc_has_before_insert_row_trig;
	bool		fc_has_instead_insert_row_trig;
	bool		fc_leafpart_use_multi_insert = false;

	Assert(fc_cstate->rel);
	Assert(list_length(fc_cstate->range_table) == 1);

	/*
	 * 目标必须是一个普通的、外部的或分区的关系，或者具有替代插入行触发器。 
	 * （目前，这样的触发器仅在视图上允许，因此我们只在视图情况下提示关于它们的信息。）
	 */
	if (fc_cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
		fc_cstate->rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
		fc_cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
		!(fc_cstate->rel->trigdesc &&
		  fc_cstate->rel->trigdesc->trig_insert_instead_row))
	{
		if (fc_cstate->rel->rd_rel->relkind == RELKIND_VIEW)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy to view \"%s\"",
							RelationGetRelationName(fc_cstate->rel)),
					 errhint("To enable copying to a view, provide an INSTEAD OF INSERT trigger.")));
		else if (fc_cstate->rel->rd_rel->relkind == RELKIND_MATVIEW)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy to materialized view \"%s\"",
							RelationGetRelationName(fc_cstate->rel))));
		else if (fc_cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy to sequence \"%s\"",
							RelationGetRelationName(fc_cstate->rel))));
		else
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy to non-table relation \"%s\"",
							RelationGetRelationName(fc_cstate->rel))));
	}

	/*
	 * 如果目标文件是在事务中新增的，我们假设检查 FSM 的自由空间是浪费时间。
	 * 这可能是错误的，但不太可能。
	 */
	if (RELKIND_HAS_STORAGE(fc_cstate->rel->rd_rel->relkind) &&
		(fc_cstate->rel->rd_createSubid != InvalidSubTransactionId ||
		 fc_cstate->rel->rd_firstRelfilenodeSubid != InvalidSubTransactionId))
		fc_ti_options |= TABLE_INSERT_SKIP_FSM;

	/*
	 * 如果在此子事务中创建了新的 relfilenode 或其已提交子事务中的一个，并且我们不会在先前的扫描或命令中看到这些行，则进行优化。子事务测试确保如果此子事务中止，则冻结的行在 xact 清理后将不可见。 请注意，确切哪个子事务创建它的更强测试对于此优化的正确性至关重要。对于早期扫描或命令的测试容忍误报。FREEZE 使其他会话看到他们在 MVCC 下看不到的行，而误报仅仅是将这种异常传播到当前会话。
	 */
	if (fc_cstate->opts.freeze)
	{
		/*
		 * 我们目前禁止在分区表上使用 COPY FREEZE。其原因在于我们尚未打开分区以确定是否可以将该优化应用于它们。我们可以在这里打开所有分区，但这样做对于小复制可能会导致相当高的开销。无论如何，我们可能最终只是将元组路由到少数几个分区。对于分区表来说，抛出一个 ERROR 似乎更好。
		 */
		if (fc_cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		{
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cannot perform COPY FREEZE on a partitioned table")));
		}

		/*
		 * 容许一次注册以便于 FirstXactSnapshot。承载扫描的查询通常会创建至少两个注册，但依赖于此是脆弱的，忽略 ActiveSnapshot 也是如此。清除 CatalogSnapshot 以避免计算其注册。我们仍将检测到正在进行的目录扫描，每个扫描单独注册其使用的快照。
		 */
		InvalidateCatalogSnapshot();
		if (!ThereAreNoPriorRegisteredSnapshots() || !ThereAreNoReadyPortals())
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
					 errmsg("cannot perform COPY FREEZE because of prior transaction activity")));

		if (fc_cstate->rel->rd_createSubid != GetCurrentSubTransactionId() &&
			fc_cstate->rel->rd_newRelfilenodeSubid != GetCurrentSubTransactionId())
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("cannot perform COPY FREEZE because the table was not created or truncated in the current subtransaction")));

		fc_ti_options |= TABLE_INSERT_FROZEN;
	}

	/*
	 * 我们需要一个 ResultRelInfo，以便可以使用常规执行器的索引条目创建机制。（这里以前有大量代码，基本上重复了 execUtils.c 的内容……）
	 */
	ExecInitRangeTable(fc_estate, fc_cstate->range_table);
	fc_resultRelInfo = fc_target_resultRelInfo = makeNode(ResultRelInfo);
	ExecInitResultRelation(fc_estate, fc_resultRelInfo, 1);

	/* 验证命名的关系是 INSERT 的有效目标 */
	CheckValidResultRel(fc_resultRelInfo, CMD_INSERT);

	ExecOpenIndices(fc_resultRelInfo, false);

	/*
	 * 设置一个 ModifyTableState，以便我们可以让 FDW(s) 为外部表结果关系初始化自己。
	 */
	fc_mtstate = makeNode(ModifyTableState);
	fc_mtstate->ps.plan = NULL;
	fc_mtstate->ps.state = fc_estate;
	fc_mtstate->operation = CMD_INSERT;
	fc_mtstate->mt_nrels = 1;
	fc_mtstate->resultRelInfo = fc_resultRelInfo;
	fc_mtstate->rootResultRelInfo = fc_resultRelInfo;

	if (fc_resultRelInfo->ri_FdwRoutine != NULL &&
		fc_resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
		fc_resultRelInfo->ri_FdwRoutine->BeginForeignInsert(fc_mtstate,
														 fc_resultRelInfo);

	/* 准备捕获 AFTER 触发器。 */
	AfterTriggerBeginQuery();

	/*
	 * 如果命名的关系上有任何带有过渡表的触发器，我们需要准备好捕获过渡元组。
	 *
	 * 由于分区元组路由希望知道过渡捕获是否处于活动状态，我们还将在 mtstate 中设置它，该状态将传递给下面的 ExecFindPartition()。
	 */
	fc_cstate->transition_capture = fc_mtstate->mt_transition_capture =
		MakeTransitionCaptureState(fc_cstate->rel->trigdesc,
								   RelationGetRelid(fc_cstate->rel),
								   CMD_INSERT);

	/*
	 * 如果命名的关系是分区表，初始化 CopyFrom 元组路由的状态。
	 */
	if (fc_cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		fc_proute = ExecSetupPartitionTupleRouting(fc_estate, fc_cstate->rel);

	if (fc_cstate->whereClause)
		fc_cstate->qualexpr = ExecInitQual(castNode(List, fc_cstate->whereClause),
										&fc_mtstate->ps);

	/*
	 * 通常，准备一堆元组以进行插入，然后在一次 table_multi_insert() 调用中插入它们，比为每个元组单独调用 table_tuple_insert() 更有效。然而，我们可能无法做到这一点的原因有很多。这些将在下面解释。
	 */
	if (fc_resultRelInfo->ri_TrigDesc != NULL &&
		(fc_resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
		 fc_resultRelInfo->ri_TrigDesc->trig_insert_instead_row))
	{
		/*
		 * 当表上有任何 BEFORE/INSTEAD OF 触发器时，无法支持多重插入。这些触发器可能会查询我们正在插入的表，并在已处理并准备好插入的元组不存在时采取不同的行动。
		 */
		fc_insertMethod = CIM_SINGLE;
	}
	else if (fc_proute != NULL && fc_resultRelInfo->ri_TrigDesc != NULL &&
			 fc_resultRelInfo->ri_TrigDesc->trig_insert_new_table)
	{
		/*
		 * 对于分区表，当存在任何语句级插入触发器时，我们无法支持多重插入。未来可能允许有此类触发器的分区表，但目前，CopyMultiInsertInfoFlush 期望任何行后插入和语句级插入触发器都在同一关系上。
		 */
		fc_insertMethod = CIM_SINGLE;
	}
	else if (fc_resultRelInfo->ri_FdwRoutine != NULL ||
			 fc_cstate->volatile_defexprs)
	{
		/*
		 * 如果是外部表或者表中有任何可变默认表达式，也无法支持多重插入。与上述触发器的情况类似，这类表达式可能会查询我们正在插入的表。
		 *
		 * 注意：如果任何分区有任何可变默认表达式也无所谓，因为我们使用的是 COPY 命令目标中的默认值。
		 */
		fc_insertMethod = CIM_SINGLE;
	}
	else if (contain_volatile_functions(fc_cstate->whereClause))
	{
		/*
		 * 如果 WHERE 子句中有任何可变函数表达式，无法支持多重插入。与上述触发器情况类似，这类表达式可能会查询我们正在插入的表。
		 *
		 * 注意：whereClause 已在 DoCopy() 中预处理，因此可以直接使用 contain_volatile_functions()。
		 */
		fc_insertMethod = CIM_SINGLE;
	}
	else
	{
		/*
		 * 对于分区表，我们可能仍然能够执行批量插入。然而，这种可能性取决于分区上存在的触发器类型。如果分区是外部表，或者有任何行前插入或插入替代触发器（与我们之前检查的父表相同），我们必须禁用批量插入。由于分区的 resultRelInfos 仅在我们实际需要向其中插入第一个元组时初始化，我们必须使用 CIM_MULTI_CONDITIONAL 的中间插入方法来标记我们稍后需要确定是否可以对正在插入的分区使用批量插入。
		 */
		if (fc_proute)
			fc_insertMethod = CIM_MULTI_CONDITIONAL;
		else
			fc_insertMethod = CIM_MULTI;

		fc_CopyMultiInsertInfoInit(&fc_multiInsertInfo, fc_resultRelInfo, fc_cstate,
								fc_estate, fc_mycid, fc_ti_options);
	}

	/*
	 * 如果不使用批处理模式（根据需要分配槽），也设置一个元组槽。插入到分区表时，即使我们可能批量插入，也需要一个，以读取根分区的元组形式。
	 */
	if (fc_insertMethod == CIM_SINGLE || fc_insertMethod == CIM_MULTI_CONDITIONAL)
	{
		fc_singleslot = table_slot_create(fc_resultRelInfo->ri_RelationDesc,
									   &fc_estate->es_tupleTable);
		fc_bistate = GetBulkInsertState();
	}

	fc_has_before_insert_row_trig = (fc_resultRelInfo->ri_TrigDesc &&
								  fc_resultRelInfo->ri_TrigDesc->trig_insert_before_row);

	fc_has_instead_insert_row_trig = (fc_resultRelInfo->ri_TrigDesc &&
								   fc_resultRelInfo->ri_TrigDesc->trig_insert_instead_row);

	/*
	 * 检查 BEFORE STATEMENT 插入触发器。是否应该对 COPY 执行此操作尚有争议，因为这并不真正是“INSERT”语句。然而，执行这些触发器保持了与我们在 COPY 上已触发的每行触发器的一致性。
	 */
	ExecBSInsertTriggers(fc_estate, fc_resultRelInfo);

	fc_econtext = GetPerTupleExprContext(fc_estate);

	/* 设置回调以识别错误行号 */
	fc_errcallback.callback = CopyFromErrorCallback;
	fc_errcallback.arg = (void *) fc_cstate;
	fc_errcallback.previous = error_context_stack;
	error_context_stack = &fc_errcallback;

	for (;;)
	{
		TupleTableSlot *fc_myslot;
		bool		fc_skip_tuple;

		CHECK_FOR_INTERRUPTS();

		/*
		 * 重置每元组的表达式上下文。我们在每个元组后执行此操作，以清理表达式评估等后的状态。
		 */
		ResetPerTupleExprContext(fc_estate);

		/* 选择槽以（初始）加载行 */
		if (fc_insertMethod == CIM_SINGLE || fc_proute)
		{
			fc_myslot = fc_singleslot;
			Assert(fc_myslot != NULL);
		}
		else
		{
			Assert(fc_resultRelInfo == fc_target_resultRelInfo);
			Assert(fc_insertMethod == CIM_MULTI);

			fc_myslot = fc_CopyMultiInsertInfoNextFreeSlot(&fc_multiInsertInfo,
													 fc_resultRelInfo);
		}

		/*
		 * 在调用 NextCopyFrom 之前切换到每元组上下文，因为它会评估默认表达式等，并要求每元组上下文。
		 */
		MemoryContextSwitchTo(GetPerTupleMemoryContext(fc_estate));

		ExecClearTuple(fc_myslot);

		/* 直接将值/空值数组存储在槽中 */
		if (!NextCopyFrom(fc_cstate, fc_econtext, fc_myslot->tts_values, fc_myslot->tts_isnull))
			break;

		ExecStoreVirtualTuple(fc_myslot);

		/*
		 * 约束和 WHERE 子句可能会引用 tableoid 列，因此在评估它们之前（重新）初始化 tts_tableOid。
		 */
		fc_myslot->tts_tableOid = RelationGetRelid(fc_target_resultRelInfo->ri_RelationDesc);

		/* 触发器和其他内容需要在查询上下文中调用。 */
		MemoryContextSwitchTo(fc_oldcontext);

		if (fc_cstate->whereClause)
		{
			fc_econtext->ecxt_scantuple = fc_myslot;
			/* 跳过不匹配COPY的WHERE子句的项目 */
			if (!ExecQual(fc_cstate->qualexpr, fc_econtext))
			{
				/*
				 * 报告该元组被WHERE子句过滤掉。
				 */
				pgstat_progress_update_param(PROGRESS_COPY_TUPLES_EXCLUDED,
											 ++fc_excluded);
				continue;
			}
		}

		/* 确定将元组插入到哪个分区 */
		if (fc_proute)
		{
			TupleConversionMap *fc_map;

			/*
			 * 尝试找到适合该元组的分区。
			 * ExecFindPartition()如果找不到或者找到的分区不适合INSERT，将引发错误。
			 */
			fc_resultRelInfo = ExecFindPartition(fc_mtstate, fc_target_resultRelInfo,
											  fc_proute, fc_myslot, fc_estate);

			if (fc_prevResultRelInfo != fc_resultRelInfo)
			{
				/* 确定该分区上存在哪些触发器 */
				fc_has_before_insert_row_trig = (fc_resultRelInfo->ri_TrigDesc &&
											  fc_resultRelInfo->ri_TrigDesc->trig_insert_before_row);

				fc_has_instead_insert_row_trig = (fc_resultRelInfo->ri_TrigDesc &&
											   fc_resultRelInfo->ri_TrigDesc->trig_insert_instead_row);

				/*
				 * 当分区具有BEFORE/INSTEAD OF触发器，或者如果分区是外部分区时，禁用多插入。
				 */
				fc_leafpart_use_multi_insert = fc_insertMethod == CIM_MULTI_CONDITIONAL &&
					!fc_has_before_insert_row_trig &&
					!fc_has_instead_insert_row_trig &&
					fc_resultRelInfo->ri_FdwRoutine == NULL;

				/* 设置该分区使用的多插入缓冲区。 */
				if (fc_leafpart_use_multi_insert)
				{
					if (fc_resultRelInfo->ri_CopyMultiInsertBuffer == NULL)
						fc_CopyMultiInsertInfoSetupBuffer(&fc_multiInsertInfo,
													   fc_resultRelInfo);
				}
				else if (fc_insertMethod == CIM_MULTI_CONDITIONAL &&
						 !fc_CopyMultiInsertInfoIsEmpty(&fc_multiInsertInfo))
				{
					/*
					 * 如果该分区不能使用批量处理，则刷新待处理的插入，以便触发器等可见行。
					 */
					fc_CopyMultiInsertInfoFlush(&fc_multiInsertInfo, fc_resultRelInfo);
				}

				if (fc_bistate != NULL)
					ReleaseBulkInsertStatePin(fc_bistate);
				fc_prevResultRelInfo = fc_resultRelInfo;
			}

			/*
			 * 如果我们正在捕获过渡元组，则可能需要从分区行类型转换为根行类型。
			 * 但如果在分区上没有BEFORE触发器可以更改元组，
			 * 我们可以记住原始未转换元组，以避免不必要的往返转换。
			 */
			if (fc_cstate->transition_capture != NULL)
				fc_cstate->transition_capture->tcs_original_insert_tuple =
					!fc_has_before_insert_row_trig ? fc_myslot : NULL;

			/*
			 * 我们可能需要从根行类型转换为分区行类型。
			 */
			fc_map = fc_resultRelInfo->ri_RootToPartitionMap;
			if (fc_insertMethod == CIM_SINGLE || !fc_leafpart_use_multi_insert)
			{
				/* 非批量插入 */
				if (fc_map != NULL)
				{
					TupleTableSlot *fc_new_slot;

					fc_new_slot = fc_resultRelInfo->ri_PartitionTupleSlot;
					fc_myslot = execute_attr_map_slot(fc_map->attrMap, fc_myslot, fc_new_slot);
				}
			}
			else
			{
				/*
				 * 准备将元组排队，以便稍后批量插入到当前分区中。
				 */
				TupleTableSlot *fc_batchslot;

				/* 分区表没有其他可用路径 */
				Assert(fc_insertMethod == CIM_MULTI_CONDITIONAL);

				fc_batchslot = fc_CopyMultiInsertInfoNextFreeSlot(&fc_multiInsertInfo,
															fc_resultRelInfo);

				if (fc_map != NULL)
					fc_myslot = execute_attr_map_slot(fc_map->attrMap, fc_myslot,
												   fc_batchslot);
				else
				{
					/*
					 * 这看起来比实际更昂贵（相信我，我已经优化掉了。两次。）。输入是虚拟形式，我们会在下面实现插槽——对于大多数插槽类型，复制执行的工作实际上正是后续实现所需的。
					 */
					ExecCopySlot(fc_batchslot, fc_myslot);
					fc_myslot = fc_batchslot;
				}
			}

			/* 确保触发器等能看到正确的关系 */
			fc_myslot->tts_tableOid = RelationGetRelid(fc_resultRelInfo->ri_RelationDesc);
		}

		fc_skip_tuple = false;

		/* BEFORE ROW INSERT 触发器 */
		if (fc_has_before_insert_row_trig)
		{
			if (!ExecBRInsertTriggers(fc_estate, fc_resultRelInfo, fc_myslot))
				fc_skip_tuple = true;	/* "不做任何操作" */
		}

		if (!fc_skip_tuple)
		{
			/*
			 * 如果有INSTEAD OF INSERT ROW触发器，让它处理元组。
			 * 否则，继续将元组插入表或外部表中。
			 */
			if (fc_has_instead_insert_row_trig)
			{
				ExecIRInsertTriggers(fc_estate, fc_resultRelInfo, fc_myslot);
			}
			else
			{
				/* 计算存储生成的列 */
				if (fc_resultRelInfo->ri_RelationDesc->rd_att->constr &&
					fc_resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
					ExecComputeStoredGenerated(fc_resultRelInfo, fc_estate, fc_myslot,
											   CMD_INSERT);

				/*
				 * 如果目标是普通表，请检查元组的约束。
				 */
				if (fc_resultRelInfo->ri_FdwRoutine == NULL &&
					fc_resultRelInfo->ri_RelationDesc->rd_att->constr)
					ExecConstraints(fc_resultRelInfo, fc_myslot, fc_estate);

				/*
				 * 如果有分区约束，也请检查元组；
				 * 除非我们是通过元组路由来到这里，如果分区上没有定义BR触发器，则不需要。
				 */
				if (fc_resultRelInfo->ri_RelationDesc->rd_rel->relispartition &&
					(fc_proute == NULL || fc_has_before_insert_row_trig))
					ExecPartitionCheck(fc_resultRelInfo, fc_myslot, fc_estate, true);

				/* 将插槽存储在启用的多插入缓冲区中。 */
				if (fc_insertMethod == CIM_MULTI || fc_leafpart_use_multi_insert)
				{
					/*
					 * 之前的插槽可能指向每个元组上下文。为了批量处理，它需要更长的生命周期。
					 */
					ExecMaterializeSlot(fc_myslot);

					/* 将该元组添加到元组缓冲区 */
					fc_CopyMultiInsertInfoStore(&fc_multiInsertInfo,
											 fc_resultRelInfo, fc_myslot,
											 fc_cstate->line_buf.len,
											 fc_cstate->cur_lineno);

					/*
					 * 如果已经排队了足够的插入，则刷新所有缓冲区到其表中。
					 */
					if (fc_CopyMultiInsertInfoIsFull(&fc_multiInsertInfo))
						fc_CopyMultiInsertInfoFlush(&fc_multiInsertInfo, fc_resultRelInfo);
				}
				else
				{
					List	   *fc_recheckIndexes = NIL;

					/* 好的，存储元组 */
					if (fc_resultRelInfo->ri_FdwRoutine != NULL)
					{
						fc_myslot = fc_resultRelInfo->ri_FdwRoutine->ExecForeignInsert(fc_estate,
																				 fc_resultRelInfo,
																				 fc_myslot,
																				 NULL);

						if (fc_myslot == NULL) /* "不做任何操作" */
							continue;	/* 请下一个元组 */

						/*
						 * AFTER ROW 触发器可能引用 tableoid
						 * 列，因此在评估它们之前需要（重新）初始化 tts_tableOid。
						 */
						fc_myslot->tts_tableOid = RelationGetRelid(fc_resultRelInfo->ri_RelationDesc);
					}
					else
					{
						/* 好的，存储元组并为其创建索引条目 */
						table_tuple_insert(fc_resultRelInfo->ri_RelationDesc,
										   fc_myslot, fc_mycid, fc_ti_options, fc_bistate);

						if (fc_resultRelInfo->ri_NumIndices > 0)
							fc_recheckIndexes = ExecInsertIndexTuples(fc_resultRelInfo,
																   fc_myslot,
																   fc_estate,
																   false,
																   false,
																   NULL,
																   NIL);
					}

					/* AFTER ROW INSERT 触发器 */
					ExecARInsertTriggers(fc_estate, fc_resultRelInfo, fc_myslot,
										 fc_recheckIndexes, fc_cstate->transition_capture);

					list_free(fc_recheckIndexes);
				}
			}

			/*
			 * 我们只计算未被 BEFORE INSERT 触发器
			 * 或 FDW 抑制的元组；这与 nodeModifyTable.c
			 * 用于计算由 INSERT 命令插入的元组的定义相同。更新
			 * COPY 命令的进度。
			 */
			pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED,
										 ++fc_processed);
		}
	}

	/* 刷新任何剩余的缓冲元组 */
	if (fc_insertMethod != CIM_SINGLE)
	{
		if (!fc_CopyMultiInsertInfoIsEmpty(&fc_multiInsertInfo))
			fc_CopyMultiInsertInfoFlush(&fc_multiInsertInfo, NULL);
	}

	/* 完成，清理 */
	error_context_stack = fc_errcallback.previous;

	if (fc_bistate != NULL)
		FreeBulkInsertState(fc_bistate);

	MemoryContextSwitchTo(fc_oldcontext);

	/* 执行 AFTER STATEMENT 插入触发器 */
	ExecASInsertTriggers(fc_estate, fc_target_resultRelInfo, fc_cstate->transition_capture);

	/* 处理排队的 AFTER 触发器 */
	AfterTriggerEndQuery(fc_estate);

	ExecResetTupleTable(fc_estate->es_tupleTable, false);

	/* 允许 FDW 关闭 */
	if (fc_target_resultRelInfo->ri_FdwRoutine != NULL &&
		fc_target_resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
		fc_target_resultRelInfo->ri_FdwRoutine->EndForeignInsert(fc_estate,
															  fc_target_resultRelInfo);

	/* 拆解多插入缓冲区数据 */
	if (fc_insertMethod != CIM_SINGLE)
		fc_CopyMultiInsertInfoCleanup(&fc_multiInsertInfo);

	/* 关闭所有分区表、叶分区及其索引 */
	if (fc_proute)
		ExecCleanupTupleRouting(fc_mtstate, fc_proute);

	/* 关闭结果关系，包括任何触发器目标关系 */
	ExecCloseResultRelations(fc_estate);
	ExecCloseRangeTableRelations(fc_estate);

	FreeExecutorState(fc_estate);

	return fc_processed;
}

/*
 * 设置从文件中读取元组以进行 COPY FROM。
 *
 * 'rel': 用作元组的模板
 * 'whereClause': 来自 COPY FROM 命令的 WHERE 子句
 * 'filename': 要读取的服务器本地文件的名称，NULL 表示 STDIN
 * 'is_program': 如果 'filename' 是要执行的程序，则为 true
 * 'data_source_cb': 提供输入数据的回调
 * 'attnamelist': 包含的 char * 列表。NIL 选择所有列。
 * 'options': DefElem 的列表。有关选择，请参见 gram.y 中的 copy_opt_item。
 *
 * 返回一个 CopyFromState，将其传递给 NextCopyFrom 和相关函数。
 */
CopyFromState
BeginCopyFrom(ParseState *fc_pstate,
			  Relation fc_rel,
			  Node *fc_whereClause,
			  const char *fc_filename,
			  bool fc_is_program,
			  copy_data_source_cb fc_data_source_cb,
			  List *fc_attnamelist,
			  List *fc_options)
{
	CopyFromState fc_cstate;
	bool		fc_pipe = (fc_filename == NULL);
	TupleDesc	fc_tupDesc;
	AttrNumber	fc_num_phys_attrs,
				fc_num_defaults;
	FmgrInfo   *fc_in_functions;
	Oid		   *fc_typioparams;
	int			fc_attnum;
	Oid			fc_in_func_oid;
	int		   *fc_defmap;
	ExprState **fc_defexprs;
	MemoryContext fc_oldcontext;
	bool		fc_volatile_defexprs;
	const int	fc_progress_cols[] = {
		PROGRESS_COPY_COMMAND,
		PROGRESS_COPY_TYPE,
		PROGRESS_COPY_BYTES_TOTAL
	};
	int64		fc_progress_vals[] = {
		PROGRESS_COPY_COMMAND_FROM,
		0,
		0
	};

	/* 分配工作空间并将所有字段置为零 */
	fc_cstate = (CopyFromStateData *) palloc0(sizeof(CopyFromStateData));

	/*
	 * 我们在一个新的内存上下文中分配 cstate 使用的所有内容。这
	 * 避免在查询中重复使用 COPY 时的内存泄漏。
	 */
	fc_cstate->copycontext = AllocSetContextCreate(CurrentMemoryContext,
												"COPY",
												ALLOCSET_DEFAULT_SIZES);

	fc_oldcontext = MemoryContextSwitchTo(fc_cstate->copycontext);

	/* 从语句节点树中提取选项 */
	ProcessCopyOptions(fc_pstate, &fc_cstate->opts, true /* is_from */ , fc_options);

	/* 处理目标关系 */
	fc_cstate->rel = fc_rel;

	fc_tupDesc = RelationGetDescr(fc_cstate->rel);

	/* 处理公共选项或初始化 */

	/* 生成或转换待处理的属性列表 */
	fc_cstate->attnumlist = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_attnamelist);

	fc_num_phys_attrs = fc_tupDesc->natts;

	/* 将 FORCE_NOT_NULL 名称列表转换为逐列标志，检查有效性 */
	fc_cstate->opts.force_notnull_flags = (bool *) palloc0(fc_num_phys_attrs * sizeof(bool));
	if (fc_cstate->opts.force_notnull)
	{
		List	   *fc_attnums;
		ListCell   *fc_cur;

		fc_attnums = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_cstate->opts.force_notnull);

		foreach(fc_cur, fc_attnums)
		{
			int			fc_attnum = lfirst_int(fc_cur);
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

			if (!list_member_int(fc_cstate->attnumlist, fc_attnum))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("FORCE_NOT_NULL column \"%s\" not referenced by COPY",
								NameStr(fc_attr->attname))));
			fc_cstate->opts.force_notnull_flags[fc_attnum - 1] = true;
		}
	}

	/* 将 FORCE_NULL 名称列表转换为逐列标志，检查有效性 */
	fc_cstate->opts.force_null_flags = (bool *) palloc0(fc_num_phys_attrs * sizeof(bool));
	if (fc_cstate->opts.force_null)
	{
		List	   *fc_attnums;
		ListCell   *fc_cur;

		fc_attnums = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_cstate->opts.force_null);

		foreach(fc_cur, fc_attnums)
		{
			int			fc_attnum = lfirst_int(fc_cur);
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

			if (!list_member_int(fc_cstate->attnumlist, fc_attnum))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("FORCE_NULL column \"%s\" not referenced by COPY",
								NameStr(fc_attr->attname))));
			fc_cstate->opts.force_null_flags[fc_attnum - 1] = true;
		}
	}

	/* 将 convert_selectively 名称列表转换为逐列标志 */
	if (fc_cstate->opts.convert_selectively)
	{
		List	   *fc_attnums;
		ListCell   *fc_cur;

		fc_cstate->convert_select_flags = (bool *) palloc0(fc_num_phys_attrs * sizeof(bool));

		fc_attnums = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_cstate->opts.convert_select);

		foreach(fc_cur, fc_attnums)
		{
			int			fc_attnum = lfirst_int(fc_cur);
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

			if (!list_member_int(fc_cstate->attnumlist, fc_attnum))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg_internal("selected column \"%s\" not referenced by COPY",
										 NameStr(fc_attr->attname))));
			fc_cstate->convert_select_flags[fc_attnum - 1] = true;
		}
	}

	/* 当未指定 ENCODING 选项时使用客户端编码。 */
	if (fc_cstate->opts.file_encoding < 0)
		fc_cstate->file_encoding = pg_get_client_encoding();
	else
		fc_cstate->file_encoding = fc_cstate->opts.file_encoding;

	/*
	 * 查找编码转换函数。
	 */
	if (fc_cstate->file_encoding == GetDatabaseEncoding() ||
		fc_cstate->file_encoding == PG_SQL_ASCII ||
		GetDatabaseEncoding() == PG_SQL_ASCII)
	{
		fc_cstate->need_transcoding = false;
	}
	else
	{
		fc_cstate->need_transcoding = true;
		fc_cstate->conversion_proc = FindDefaultConversionProc(fc_cstate->file_encoding,
															GetDatabaseEncoding());
		if (!OidIsValid(fc_cstate->conversion_proc))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_FUNCTION),
					 errmsg("default conversion function for encoding \"%s\" to \"%s\" does not exist",
							pg_encoding_to_char(fc_cstate->file_encoding),
							pg_encoding_to_char(GetDatabaseEncoding()))));
	}

	fc_cstate->copy_src = COPY_FILE;	/* 默认 */

	fc_cstate->whereClause = fc_whereClause;

	/* 初始化状态变量 */
	fc_cstate->eol_type = EOL_UNKNOWN;
	fc_cstate->cur_relname = RelationGetRelationName(fc_cstate->rel);
	fc_cstate->cur_lineno = 0;
	fc_cstate->cur_attname = NULL;
	fc_cstate->cur_attval = NULL;

	/*
	 * 为输入管道分配缓冲区。
	 *
	 * attribute_buf 和 raw_buf 在文本和二进制模式下均使用，
	 * 但 input_buf 和 line_buf 仅在文本模式下使用。
	 */
	fc_cstate->raw_buf = palloc(RAW_BUF_SIZE + 1);
	fc_cstate->raw_buf_index = fc_cstate->raw_buf_len = 0;
	fc_cstate->raw_reached_eof = false;

	if (!fc_cstate->opts.binary)
	{
		/*
		 * 如果需要编码转换，我们需要另一个缓冲区来保存
		 * 转换后的输入数据。否则，我们可以将 input_buf
		 * 指向与 raw_buf 相同的缓冲区。
		 */
		if (fc_cstate->need_transcoding)
		{
			fc_cstate->input_buf = (char *) palloc(INPUT_BUF_SIZE + 1);
			fc_cstate->input_buf_index = fc_cstate->input_buf_len = 0;
		}
		else
			fc_cstate->input_buf = fc_cstate->raw_buf;
		fc_cstate->input_reached_eof = false;

		initStringInfo(&fc_cstate->line_buf);
	}

	initStringInfo(&fc_cstate->attribute_buf);

	/* 分配范围表，我们将在 CopyFrom 中需要它。 */
	if (fc_pstate)
		fc_cstate->range_table = fc_pstate->p_rtable;

	fc_tupDesc = RelationGetDescr(fc_cstate->rel);
	fc_num_phys_attrs = fc_tupDesc->natts;
	fc_num_defaults = 0;
	fc_volatile_defexprs = false;

	/*
	 * 获取每个属性在关系中的所需目录信息，包括输入函数、元素类型（传递给输入函数）以及有关默认值和约束的信息。（我们使用的输入函数取决于文本/二进制格式的选择。）
	 */
	fc_in_functions = (FmgrInfo *) palloc(fc_num_phys_attrs * sizeof(FmgrInfo));
	fc_typioparams = (Oid *) palloc(fc_num_phys_attrs * sizeof(Oid));
	fc_defmap = (int *) palloc(fc_num_phys_attrs * sizeof(int));
	fc_defexprs = (ExprState **) palloc(fc_num_phys_attrs * sizeof(ExprState *));

	for (fc_attnum = 1; fc_attnum <= fc_num_phys_attrs; fc_attnum++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

		/* 我们不需要已丢弃属性的信息 */
		if (fc_att->attisdropped)
			continue;

		/* 获取输入函数和typio参数信息 */
		if (fc_cstate->opts.binary)
			getTypeBinaryInputInfo(fc_att->atttypid,
								   &fc_in_func_oid, &fc_typioparams[fc_attnum - 1]);
		else
			getTypeInputInfo(fc_att->atttypid,
							 &fc_in_func_oid, &fc_typioparams[fc_attnum - 1]);
		fmgr_info(fc_in_func_oid, &fc_in_functions[fc_attnum - 1]);

		/* 如有必要，获取默认信息 */
		if (!list_member_int(fc_cstate->attnumlist, fc_attnum) && !fc_att->attgenerated)
		{
			/* 属性不应从输入中复制 */
			/* 如果存在，则使用默认值 */
			Expr	   *fc_defexpr = (Expr *) build_column_default(fc_cstate->rel,
																fc_attnum);

			if (fc_defexpr != NULL)
			{
				/* 通过计划运行表达式 */
				fc_defexpr = expression_planner(fc_defexpr);

				/* 在复制上下文中初始化可执行表达式 */
				fc_defexprs[fc_num_defaults] = ExecInitExpr(fc_defexpr, NULL);
				fc_defmap[fc_num_defaults] = fc_attnum - 1;
				fc_num_defaults++;

				/*
				 * 如果默认表达式查看正在加载的表，那么在使用
				 * multi-insert时可能会给出错误的答案。由于数据库访问
				 * 可以是动态的，因此很难准确测试，因此我们使用
				 * 更广泛的测试来判断默认表达式是否是volatile的。我们允许
				 * 默认表达式为序列的nextval()的特例，在这种特定情况下
				 * 已知可以安全地用于multi-insert优化。因此我们使用
				 * 这个特例函数检查器，而不是标准的
				 * contain_volatile_functions()检查。请注意，我们已经
				 * 通过expression_planner()运行了表达式。
				 */
				if (!fc_volatile_defexprs)
					fc_volatile_defexprs = contain_volatile_functions_not_nextval((Node *) fc_defexpr);
			}
		}
	}


	/* 初始化进度 */
	pgstat_progress_start_command(PROGRESS_COMMAND_COPY,
								  fc_cstate->rel ? RelationGetRelid(fc_cstate->rel) : InvalidOid);
	fc_cstate->bytes_processed = 0;

	/* 我们将这些变量保存在cstate中。 */
	fc_cstate->in_functions = fc_in_functions;
	fc_cstate->typioparams = fc_typioparams;
	fc_cstate->defmap = fc_defmap;
	fc_cstate->defexprs = fc_defexprs;
	fc_cstate->volatile_defexprs = fc_volatile_defexprs;
	fc_cstate->num_defaults = fc_num_defaults;
	fc_cstate->is_program = fc_is_program;

	if (fc_data_source_cb)
	{
		fc_progress_vals[1] = PROGRESS_COPY_TYPE_CALLBACK;
		fc_cstate->copy_src = COPY_CALLBACK;
		fc_cstate->data_source_cb = fc_data_source_cb;
	}
	else if (fc_pipe)
	{
		fc_progress_vals[1] = PROGRESS_COPY_TYPE_PIPE;
		Assert(!fc_is_program);	/* 语法不允许这样做 */
		if (whereToSendOutput == DestRemote)
			ReceiveCopyBegin(fc_cstate);
		else
			fc_cstate->copy_file = stdin;
	}
	else
	{
		fc_cstate->filename = pstrdup(fc_filename);

		if (fc_cstate->is_program)
		{
			fc_progress_vals[1] = PROGRESS_COPY_TYPE_PROGRAM;
			fc_cstate->copy_file = OpenPipeStream(fc_cstate->filename, PG_BINARY_R);
			if (fc_cstate->copy_file == NULL)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not execute command \"%s\": %m",
								fc_cstate->filename)));
		}
		else
		{
			struct stat fc_st;

			fc_progress_vals[1] = PROGRESS_COPY_TYPE_FILE;
			fc_cstate->copy_file = AllocateFile(fc_cstate->filename, PG_BINARY_R);
			if (fc_cstate->copy_file == NULL)
			{
				/* 复制errno，因为ereport子函数可能会更改它 */
				int			fc_save_errno = errno;

				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not open file \"%s\" for reading: %m",
								fc_cstate->filename),
						 (fc_save_errno == ENOENT || fc_save_errno == EACCES) ?
						 errhint("COPY FROM instructs the PostgreSQL server process to read a file. "
								 "You may want a client-side facility such as psql's \\copy.") : 0));
			}

			if (fstat(fileno(fc_cstate->copy_file), &fc_st))
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not stat file \"%s\": %m",
								fc_cstate->filename)));

			if (S_ISDIR(fc_st.st_mode))
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is a directory", fc_cstate->filename)));

			fc_progress_vals[2] = fc_st.st_size;
		}
	}

	pgstat_progress_update_multi_param(3, fc_progress_cols, fc_progress_vals);

	if (fc_cstate->opts.binary)
	{
		/* 读取并验证二进制头 */
		ReceiveCopyBinaryHeader(fc_cstate);
	}

	/* 为CopyReadAttributes结果创建工作区 */
	if (!fc_cstate->opts.binary)
	{
		AttrNumber	fc_attr_count = list_length(fc_cstate->attnumlist);

		fc_cstate->max_fields = fc_attr_count;
		fc_cstate->raw_fields = (char **) palloc(fc_attr_count * sizeof(char *));
	}

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_cstate;
}

/*
 * 清理存储并释放COPY FROM的资源。
 */
void EndCopyFrom(CopyFromState fc_cstate)
{
	/* 除内存外，没有与COPY FROM相关的资源。 */
	if (fc_cstate->is_program)
	{
		fc_ClosePipeFromProgram(fc_cstate);
	}
	else
	{
		if (fc_cstate->filename != NULL && FreeFile(fc_cstate->copy_file))
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not close file \"%s\": %m",
							fc_cstate->filename)));
	}

	pgstat_progress_end_command();

	MemoryContextDelete(fc_cstate->copycontext);
	pfree(fc_cstate);
}

/*
 * 关闭来自外部程序的管道，检查pclose()返回代码。
 */
static void fc_ClosePipeFromProgram(CopyFromState fc_cstate)
{
	int			fc_pclose_rc;

	Assert(fc_cstate->is_program);

	fc_pclose_rc = ClosePipeStream(fc_cstate->copy_file);
	if (fc_pclose_rc == -1)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close pipe to external command: %m")));
	else if (fc_pclose_rc != 0)
	{
		/*
		 * 如果我们在到达EOF之前结束了COPY FROM PROGRAM，那么被调用程序
		 * 失败并产生SIGPIPE是可以预期的，我们不应该将其报告为
		 * 错误。否则，SIGPIPE表示一个问题。
		 */
		if (!fc_cstate->raw_reached_eof &&
			wait_result_is_signal(fc_pclose_rc, SIGPIPE))
			return;

		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
				 errmsg("program \"%s\" failed",
						fc_cstate->filename),
				 errdetail_internal("%s", wait_result_to_str(fc_pclose_rc))));
	}
}
